﻿// (c) Copyright Michael Antonio.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Xml.Linq;
using Microsoft.Phone.Shell;
using MichMan.Utilities;
using MichMan.Utilities.Requests;
using Microsoft.Devices;
using System.IO;
using System.Windows.Navigation;
using Media;

namespace Subsonic.Data
{
    public interface IMusicPlayer
    {
        void Attach(object o);
        void Play();
        void Pause();
        void Stop();
        void Skip();
        void Prev();
        void Add(Child child);
        void Clear();
        void Remove(Child child);
        void Shuffle();
        double Volume { get; set; }

        event RoutedEventHandler CurrentStateChanged;
        MediaElementState CurrentState { get; }

        // Queue's a list of songs and starts playing from child indicated.  
        void AddSongs(List<Child> children, Child childToPlay);

        void ExtractHistoryItem(NavigationContext navContext);

        /// <summary>
        /// For playing an album or playlist.
        /// </summary>
        /// <param name="children"></param>
        /// <param name="child"></param>
        void PlayList(List<Child> children, Child childToPlay);

        List<Child> Queue { get; }
        int CurrentIndex { get; }
        Child CurrentSong { get; set; }

        void PlayChild(Child child);
        void OnResume();
        void OnSuspend();
    }

    public abstract class MusicPlayer : IMusicPlayer
    {
        public const string positionKey = "MediaElement_Position";
        public const string currentSongKey = "Queue_CurrentSong";

        public abstract void Attach(object o);
        public abstract void Play();
        public abstract void PlayChild(Child child);
        public abstract void Pause();
        public abstract void Stop();
        public abstract void Skip();
        public abstract void Prev();
        public abstract void Add(Child child);
        public abstract void Clear();
        public abstract void Remove(Child child);
        public abstract void Shuffle();
        public abstract double Volume { get; set; }

        public event RoutedEventHandler CurrentStateChanged;
        public abstract MediaElementState CurrentState { get; }

        // Queue's a list of songs and starts playing from child indicated.  
        public abstract void AddSongs(List<Child> children, Child childToPlay);

        public abstract void ExtractHistoryItem(NavigationContext navContext);

        /// <summary>
        /// For playing an album or playlist.
        /// </summary>
        /// <param name="children"></param>
        /// <param name="child"></param>
        public virtual void PlayList(List<Child> children, Child childToPlay)
        {
            Queue.Clear();
            AddSongs(children, childToPlay);
        }

        public abstract List<Child> Queue { get; protected set; }
        public abstract int CurrentIndex { get; protected set; }
        public abstract Child CurrentSong { get; set;  }

        public abstract void OnResume();
        public abstract void OnSuspend();

        public static void Resume()
        {
            IsolatedStorageSettingsCache issc = new IsolatedStorageSettingsCache();
            if (issc.Get("MusicPlayer_Type") != null)
            {
                _musicPlayerType = Type.GetType(issc.Get<string>("MusicPlayer_Type"));
                Instance.OnResume();
            }
        }

        public static void Suspend()
        {
            Instance.OnSuspend();
            IsolatedStorageSettingsCache issc = new IsolatedStorageSettingsCache();
            issc.Insert("MusicPlayer_Type", Instance.GetType().AssemblyQualifiedName);
        }

        static Type _musicPlayerType = typeof(MediaElementStramingPlayer);
        private static Changed<bool> musicPlayerTypeChanged = new Changed<bool>(() => SubsonicSettings.Instance.ActiveServer.Jukebox);
        public static MusicPlayer Instance
        {
            get
            {
                if (musicPlayerTypeChanged.Value)
                {
                    _musicPlayerType = SubsonicSettings.Instance.ActiveServer.Jukebox ? typeof(JukeboxPlayer) : typeof(MediaElementStramingPlayer);
                    Singleton<MusicPlayer>.Set(null);
                }

                return Singleton<MusicPlayer>.Create(() => (MusicPlayer)Activator.CreateInstance(_musicPlayerType));
            }
        }
    }

    public class NoPlayer : MusicPlayer
    {
        public override MediaElementState CurrentState { get { return default(MediaElementState); } }

        public override void Attach(object o) { }
        public override void Play() { }
        public override void AddSongs(List<Child> children, Child child) { }
        public override void ExtractHistoryItem(NavigationContext navContext) { }
        public override void Pause() { }
        public override void Stop() { }
        public override void Skip() { }
        public override void Prev() { }
        public override void Add(Child child) { }
        public override void Clear() { }
        public override void Remove(Child child) { }
        public override void Shuffle() { }
        public override double Volume { get; set; }
        public override List<Child> Queue { get { return new List<Child>(); } protected set { } }
        public override int CurrentIndex { get { return -1; } protected set { } }
        public override Child CurrentSong
        {
            get
            {
                if (CurrentIndex >= 0 && CurrentIndex < Queue.Count)
                {
                    return Queue[CurrentIndex];
                }
                return null;
            }

            set
            {
                int index = MusicPlayer.Instance.Queue.Select(c => c.Id).ToList().IndexOf(value.Id);
                if (index != -1)
                {
                    CurrentIndex = index;
                    PlayChild(CurrentSong);
                }
                // TODO:  Clear queue and play song if it is not in queue.
            }
        }

        public override void OnResume() { }
        public override void OnSuspend() { }
        public override void PlayChild(Child child) { }
    }

    public abstract class OnlinePlayer : NoPlayer
    {
        public void ProcessMultiple(Child child, Action<List<Child>> action)
        {
            if (!child.IsDir)
            {
                action(new List<Child>() { child });
            }
            else if (child.Album != null)
            {
                GetMusicDirectoryRequest req = new GetMusicDirectoryRequest(child.Id);
                req.RunRequestCompleted += (s, e) =>
                    {
                        if (req.RequestStatus == RequestStatus.Succeeded && req.Response.Directory.Children != null)
                        {
                            action(req.Response.Directory.Children);
                        }
                    };
                req.RunRequestAsync();
            }
        }
    }

    [DataContract]
    public class QueueData
    {
        [DataMember]
        public XElement Queue { get; set; }
    }

    public abstract class QueuePlayer : OnlinePlayer
    {
        public QueuePlayer()
        {
            Queue = new List<Child>();
            CurrentIndex = -1;
        }

        public override void Prev()
        {
            CurrentIndex -= 1;
            PlayCurrent();
        }

        public override void Skip()
        {
            CurrentIndex += 1;
            PlayCurrent();
        }

        protected void PlayCurrent()
        {
            Child child = Queue.ElementAtOrDefault(CurrentIndex);
            if (child != null && !child.IsDir)
            {
                PlayChild(child);
            }
        }

        public override void Add(Child child)
        {
            int idx = Queue.IndexOf(child);
            if (idx >= 0)
            {
                // it's already in the queue.  Just play it.
                CurrentIndex = idx;
                PlayCurrent();
            }
            else
            {
                // Add the item(s) to the queue and play the first one.
                ProcessMultiple(child, (l) => AddSongs(l, l.FirstOrDefault()));
            }
        }
        
        public override void AddSongs(List<Child> songs, Child song)
        {
            // Add them to the play queue
            Queue.AddRange(songs);
            CurrentIndex = Math.Max(songs.IndexOf(song), 0);
            // Play the first one.  This will change the download priority of this item to "now".
            if (songs.Count >= CurrentIndex)
            {
                PlayChild(songs[CurrentIndex]);
            }
        }

        public override void Clear() 
        { 
            Stop(); 
            Queue.Clear();
            CurrentIndex = -1; 
        }

        public override void OnSuspend()
        {
            // Save queue

            XElement xlist = new XElement(XName.Get("Items", "http://subsonic.org/restapi"));
            xlist.Add(from c in Queue select c.Element);

            // Save is called automatically on exit.
            Settings<QueueData>.Instance.Queue = xlist;

            base.OnSuspend();
        }

        public override void OnResume()
        {
            XElement xlist = Settings<QueueData>.Instance.Queue;
            if (xlist != null)
            {
                List<Child> songs = new List<Child>();
                foreach (var elt in xlist.Elements())
                {
                    songs.Add(ZPathSerializer<Child>.Load(elt));
                }
                Queue = songs;
            }
            base.OnResume();
        }

        public override List<Child> Queue { get; protected set; }
        public override int CurrentIndex { get; protected set; }
    }

    public static class ChildExtensions
    {
        public static Uri Uri(this Child child)
        {
            return new Uri(SubsonicRequestContext.StreamUrl(child.Id));
        }
    }

    public class MediaElementStramingPlayer : QueuePlayer
    {
        public MediaElementStramingPlayer()
        {
        }

        void MediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
        {
            Debug.WriteLine("MediaFailed " + e.ErrorException.ToString());
        }

        public override void Attach(object o) 
        {
            if (MediaElement != null)
            {
                MediaElement.CurrentStateChanged -= new RoutedEventHandler(MediaElement_CurrentStateChanged);
                MediaElement.MediaFailed -= new EventHandler<ExceptionRoutedEventArgs>(MediaElement_MediaFailed);
                MediaElement.MediaOpened -= new RoutedEventHandler(MediaElement_MediaOpened);
            }
            MediaElement = (MediaElement)o;
            if (MediaElement != null)
            {
                MediaElement.CurrentStateChanged += new RoutedEventHandler(MediaElement_CurrentStateChanged);
                MediaElement.MediaFailed += new EventHandler<ExceptionRoutedEventArgs>(MediaElement_MediaFailed);
                MediaElement.MediaOpened += new RoutedEventHandler(MediaElement_MediaOpened);
            }

            PhoneApplicationService.Current.Deactivated += new EventHandler<DeactivatedEventArgs>(Current_Deactivated);
            PhoneApplicationService.Current.Closing += new EventHandler<ClosingEventArgs>(Current_Closing);
        }

        void Current_Closing(object sender, ClosingEventArgs e)
        {
            OnSuspend();
        }

        void Current_Deactivated(object sender, DeactivatedEventArgs e)
        {
            OnSuspend();
        }

        void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
        {
            try
            {
                IsolatedStorageSettingsCache issc = new IsolatedStorageSettingsCache();

                if (issc.Get(positionKey) != null)
                {
                    TimeSpan? pos = issc.Get<TimeSpan>(positionKey);
                    if (pos.HasValue)
                    {
                        MediaElement.Position = pos.Value;
                        issc.Delete(positionKey);
                    }
                }
            }
            finally
            {
                MediaElement.Play();
                Debug.WriteLine("MediaOpened");
            }
        }

        void MediaElement_CurrentStateChanged(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("CurrentStateChanged to " + MediaElement.CurrentState.ToString());
            // Debug.WriteLine("IsComplete: " + this.StreamingStream.IsComplete().ToString());

            if (MediaElement.CurrentState == MediaElementState.Stopped)
            {
                CurrentIndex++;
                PlayCurrent();
            }
        }

        public MediaElement MediaElement { get; private set; }
        public Mp3MediaStreamSource MediaStreamSource { get; private set; }

        StreamingStream _stream;
        StreamingStream StreamingStream 
        {
            get { return _stream; }

            set
            {
                if (Object.ReferenceEquals(_stream, value))
                {
                    return;
                }

                if (_stream != null)
                {
                    _stream.RunRequestCompleted -= StreamingStream_RunRequestCompleted;
                }
                _stream = value;
                if (_stream != null)
                {
                    App.Current.AttachRequest(_stream.CurrentRequest);
                    _stream.RunRequestCompleted += StreamingStream_RunRequestCompleted;
                }
            }
        }

        public override void PlayChild(Child child)
        {
            if (MediaElement != null)
            {
                MediaElement.Stop();
                StreamingStream stream = this.StreamingStream;
                // See if the current StreamingStream is the correct one.  If so, use it.  If not, get a new one.
                if (stream == null)
                {
                    stream = new StreamingStream(child.Uri());
                }
                else
                {
                    if (stream.Uri != child.Uri())
                    {
                        stream = new StreamingStream(child.Uri());
                    }
                }

                StreamingStream = stream;
                MediaStreamSource = new Mp3MediaStreamSource(stream);
                MediaElement.SetSource(MediaStreamSource);
                ReportHistory(child);
            }
        }

        private void ReportHistory(Child song)
        {
            if (string.IsNullOrEmpty(song.CoverArt))
            {
                ReportHistoryInternal(song, null);
            }
            else
            {
                SubsonicCoverArtRequest req = new SubsonicCoverArtRequest() { Id = song.Id };
                req.RunRequestCompleted += (s, e) =>
                {
                    if (e.Error == null && req.Response != null)
                    {
                        ReportHistoryInternal((Child)e.UserState, req.Response);
                    }
                    else
                    {
                        ReportHistoryInternal(song, null);
                    }
                };
                req.RunRequestAsync(song);
            }
        }

        public const string songUriKey = "songUri";
        public const string songIdKey = "songId";
        protected static HistoryItem historyItem;

        protected class HistoryItem
        {
            public HistoryItem(string uri, string id)
            {
                Uri = uri;
                Id = id;
            }
            public string Uri;
            public string Id;
        }

        public override void ExtractHistoryItem(NavigationContext navContext)
        {
            if (navContext.QueryString.ContainsKey(songUriKey) && navContext.QueryString.ContainsKey(songIdKey))
            {
                historyItem = new HistoryItem(navContext.QueryString[songUriKey], navContext.QueryString[songIdKey]);
            }
        }

        private void ReportHistoryInternal(Child song, Stream imageStream)
        {
            return;
            /*
            if (imageStream == null)
            {
                imageStream = new MemoryStream();
                Stream resourceStream = Application.GetResourceStream(new Uri("Images/unknown_album.png", UriKind.Relative)).Stream;
                for (int b = resourceStream.ReadByte(); ((b = resourceStream.ReadByte()) != -1); )
                {
                    imageStream.WriteByte((byte)b);
                }
            }
            imageStream.Position = 0;

            MediaHistoryItem mediaHistoryItem = new MediaHistoryItem();

            //<hubTileImageStream> must be a valid ImageStream.
            mediaHistoryItem.ImageStream = imageStream;
            mediaHistoryItem.Title = song.Title;
            mediaHistoryItem.Source = "";
            mediaHistoryItem.PlayerContext.Add(songUriKey, song.Uri().ToString());
            mediaHistoryItem.PlayerContext.Add(songIdKey, song.Id);
            MediaHistory.Instance.WriteRecentPlay(mediaHistoryItem);
             */
        }

        /// <summary>
        /// Starts downloading the next item in the queue, if any.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void StreamingStream_RunRequestCompleted(object sender, RunRequestCompletedEventArgs e)
        {
            /*
            StreamingStream stream = (StreamingStream)sender;

            // If we're already downloading something, let it continue.
            if (!Object.ReferenceEquals(sender, StreamingStream))
            {
                return;
            }

            foreach (var child in Queue.Pivot(c => c.Id == stream.Uri.ToString()))
            {
                ManagedFileInfo mfi = FileManager.Instance.FindByUri(child.Uri().ToString());

                if (mfi == null)
                {
                    StreamingStream = new StreamingStream(child.Uri());
                    return;
                }
                else
                {
                    FileRange fr = mfi.Metadata.Get<FileRange>();
                    if (fr != null)
                    {
                        if (fr.Length == fr.Total && fr.Length != 0)
                        {
                            // Nothing to get.
                            continue;
                        }
                    }

                    StreamingStream = new StreamingStream(child.Uri());
                    return;
                }
            }
            */
        }

        public override void Play() 
        {
            if (MediaElement != null)
            {
                MediaElement.Play();
            }
        }

        public override void Pause()
        {
            if (MediaElement != null)
            {
                MediaElement.Pause();
            }
        }

        public override void Stop() 
        {
            if (MediaElement != null)
            {
                MediaElement.Stop();
            }
        }

        public override void Remove(Child child) 
        {
            if (CurrentSong == child)
            {
                Skip();
                Queue.Remove(child);
            }
        }

        public override void Shuffle() { }
        public override double Volume 
        { 
            get 
            {
                if (MediaElement != null)
                {
                    return MediaElement.Volume;
                }
                return 0;
            } 
            set 
            {
                if (MediaElement != null)
                {
                    MediaElement.Volume = value;
                }
            } 
        }

        public override void OnSuspend()
        {
            base.OnSuspend();

            IsolatedStorageSettingsCache issc = new IsolatedStorageSettingsCache();

            // Save media element information
            if (MediaElement != null)
            {
                TimeSpan pos = MediaElement.Position;
                Child song = this.CurrentSong;

                MediaElement.Pause();

                if (song != null)
                {
                    issc.Insert(positionKey, pos);
                    issc.Insert(currentSongKey, song.Id.ToString());
                }
            }
        }

        public override void OnResume()
        {
            base.OnResume();

            // Load media element information
            IsolatedStorageSettingsCache issc = new IsolatedStorageSettingsCache();

            Child song = null;

            if (historyItem != null)
            {
                // If the song isn't in the queue, clear the queue and query song info from the server.
                song = Queue.FirstOrDefault(c => c.Id == historyItem.Id);
                if (song == null)
                {
                    Queue.Clear();
                    GetMusicDirectoryRequest req = new GetMusicDirectoryRequest(historyItem.Id);
                    req.RunRequestCompleted += (s, e) =>
                        {
                            if (req.Response != null &&
                                req.Response.Directory != null &&
                                req.Response.Directory.Children != null &&
                                req.Response.Directory.Children.Count != 0)
                            {
                                Queue.AddRange(req.Response.Directory.Children);
                                CurrentIndex = 0;
                                issc.Delete(positionKey);
                                PlayChild(Queue.First());
                            }
                        };
                }
            }
            else
            {
                if (null == issc.Get(currentSongKey))
                {
                    issc.Delete(positionKey);
                    return;
                }

                string songId = issc.Get<string>(currentSongKey);
                issc.Delete(currentSongKey);
                song = Queue.FirstOrDefault(c => c.Id == songId);
            }

            if (song != null && MediaElement != null)
            {
                CurrentIndex = Queue.IndexOf(song);
                PlayChild(song);
            }
        }

    }

    public class JukeboxPlayer : QueuePlayer
    {
        public JukeboxPlayer()
        {
            OnResume();
        }

        public override void OnResume()
        {
            JukeboxControlRequest getReq = new JukeboxControlRequest(JukeboxAction.get);
            getReq.RunRequestCompleted += (s, e) =>
            {
                Queue.Clear();
                Queue.AddRange(getReq.Response.JukeboxPlaylist.Children);
                this.CurrentIndex = getReq.Response.JukeboxPlaylist.CurrentIndex;
            };
            getReq.RunRequestAsync();
        }

        public override void Play() { new JukeboxControlRequest(JukeboxAction.start).RunRequestAsync(); }
        public override void Stop() { new JukeboxControlRequest(JukeboxAction.stop).RunRequestAsync(); }
        public override void Skip() { new JukeboxControlRequest(JukeboxAction.skip).RunRequestAsync(); }
        public override void Prev() { Skip(); }
        public override void Pause() { Stop(); }

        public override void Add(Child child)
        {
            ProcessMultiple(child, JukeboxAction.add); 
        }

        public override void PlayChild(Child child)
        {
            base.PlayChild(child);
        }

        public override void PlayList(List<Child> children, Child childToPlay)
        {
            Queue.AddRange(children);
            JukeboxControlRequest clearReq = new JukeboxControlRequest(JukeboxAction.clear);
            clearReq.RunRequestCompleted += (s, e) =>
                {
                    IRequest req = ProcessList(children, JukeboxAction.add);
                    if (req != null)
                    {
                        req.RunRequestCompleted += (s1, e1) => Play();
                    }
                };
            clearReq.RunRequestAsync();
        }

        public void ProcessMultiple(Child child, JukeboxAction action)
        {
            ProcessMultiple(child, (children) =>
                {
                    ProcessList(children, action);
                });
        }

        private IRequest ProcessList(List<Child> children, JukeboxAction action)
        {
            var ids = (from c in children where !c.IsDir select c.Id).ToArray();
            if (ids.Count() != 0)
            {
                WaitForMultipleRequest<JukeboxControlRequest> waiter = new WaitForMultipleRequest<JukeboxControlRequest>();
                foreach (var id in ids)
                {
                    IRequest req = new JukeboxControlRequest(action, new[] { id });
                    req.RunRequestAsync();
                }
                waiter.RunRequestAsync();
                return waiter;
            }
            return null;
        }
        public override void Clear() { new JukeboxControlRequest(JukeboxAction.clear).RunRequestAsync(); }
        public override void Remove(Child child) { ProcessMultiple(child, JukeboxAction.remove); }
        public override void Shuffle() { new JukeboxControlRequest(JukeboxAction.shuffle).RunRequestAsync(); }

        double _volume = 0;
        public override double Volume
        {
            get
            {
                return _volume;
            }

            set
            {
                _volume = value;
                new JukeboxControlRequest(JukeboxAction.setGain) { Gain = (float)value }.RunRequestAsync();
            }
        }
    }
}
